home *** CD-ROM | disk | FTP | other *** search
Wrap
Text File | 1994-12-04 | 39.3 KB | 1,239 lines | [ TEXT/PJMM]
{From: mrob @ world . std . com ( Robert P Munafo )} { Subject : "Missile " source from 1984} {Recently in comp . sys . mac . programmer there has been some discussion } {of early Macintosh programs that still run on current machines , } {and my Missile Command game from mid - 1984 was mentioned .} {This program was written in Lisa Pascal, } {the cross - development system that everyone had to use} {until Stanford's sumacc came along,} {and has the rather charming property of continuing to run on} { all new machines that Apple comes out with .} { I ran it on my own Power Mac 6100 / 60 yesterday just for fun .} { It 's just as playable as it was on the old 128K Mac} { ( although the timing for the " Game Over " screen is now way off ,} { as I didn 't check TickCount for that ) .} {I have not done anything to this code ,} { like try to port it to any recent Pascal compiler or anything like that . } {I know that Lisa Pascal , Macintosh Pascal , } {and TML Language Systems Pascal were all reasonably } {compatible with each other;} {after that I switched over to Think C and lost track .} {The "%title " and " %subtitle " directives were} {for the print spooler on the Dartmouth mainframe that I used to } {print out listings .} {There seem to be a lot more options in the Game Options dialog} { than the actual Missile game provides .} { I 'm not sure what' s going on with that .} { - - - - - - - - - - - Cut Here - - - Pascal Source code - - - - - - - - - - -} { %title 'Missile - a game for the Apple Macintosh.' } { %subtitle 'Overall program description.' } { $X- } { Turns off A.R.S.E. } program Game; {} { Missile -- A Missile Command game for the Macintosh} { by Robert P. Munafo at Dartmouth College.} {} { I wrote this to learn a bit about Mac programming. It has a} { few 'standard user interface' bugs:} {} { It doesn't update the window during the 'End of round X' and} { 'GAME OVER' routines. (It only updates while playing.)} { It won't quit when you select "quit" while a desk accessory is} { on screen. Instead, it will wait until you close all the} { desk accessories, then quit.} { The cursor looks the same all the time. It should change} { shape to let the user know when he can and cannot fire missiles.} {} { It also has some missing features (which I hope to add soon):} {} { o Smart Bombs} { o Add an "Ammunition: [xxx]% of normal" item to the dialog box.} { o Sound} { o User should be able to pick what round the game starts with} { o High scores list saved on the disk} { o Two-player option} { o The animation needs to be made faster (in some manner)} {} { %subtitle 'USES statements' } uses QuickDraw, OSUtils, ToolUtils, Packages; { %subtitle 'Constants' } const lastMenu = 4; { number of menus } appleMenu = 1; { menu ID for desk accessory menu } fileMenu = 256; { menu ID for File menu } EditMenu = 257; { menu ID for the Edit menu. } GameMenu = 258; { menu ID for Game menu } str_1_ID = 300; {ID for my first string of text} str_2_ID = 350; {ID for my second string of text} box_ID = 450; {ID of "about" dialog box} box2_id = 451; {ID of options box} fbIdle = 0; fbGrow = 1; fbShrink = 2; maxfb = 60; { Maximum number of fireballs allowed. } fbRad = 15; { Radius of fireballs. } fbRadRate = 2; { Rate of expansion/contraction of fireballs. } msIdle = 0; msActive = 1; msNormal = 1; msMirv = 2; msSmart = 3; maxms = 20; { Maximum number of missiles allowed. } mWidth = 3; { Width of missile tracks. } CityHeight = 40; { Distance from bottom up to cities. } CityWidth = 50; { Width of cities. } BuildHeight = 30; { Maximum height of buildings. } BuildWidth = 4; { Width of buildings. } nCities = 6; { Number of cities. } type fireball = record { Data type to describe a fireball. } bounds: Rect; { Rectanggle to copy bits into. } size: INTEGER; { Current radius of fireball. } mode: INTEGER; { Idle, Growing, or shrinking. } end; missile = record { Data type to describe a missile. } start: Point; { One endpoint of the missile's path. } pos: Point; { Position within path - 0 to 1. } dh, dv: INTEGER; { Vertical and horizontal speed in pixels. } city: INTEGER; { Number of the target city. } kind: INTEGER; { MIRV or normal. } mode: INTEGER; { Idle or Active. } end; var myMenus: array[1..lastMenu] of MenuHandle; screenRect: Rect; doneFlag: BOOLEAN; myEvent: EventRecord; code, refNum: INTEGER; wRecord: WindowRecord; myWindow, whichWindow: WindowPtr; first_string, second_string: StringHandle; { Handles to strings of text for AboutMissile. } { My game variables are here. } fbBitMap: BitMap; { BitMap for fireball pictures. } fbBits: array[1..3600] of INTEGER; { Actual bits for fireball pictures. } crosshairs: Cursor; { Crosshairs cursor read in from resource. } crosshairs2: Cursor; hCurs, hCurs2: CursHandle; { Handle to the cursor. } CityPattern: PatHandle;{ Handle to pattern in which to draw cities. } FieldWidth: INTEGER; { Width of the playing field. } FieldHeight: INTEGER; { Height of the playing field. } i: INTEGER; { Loop variable. } fbs: array[1..maxfb] of fireball; { Each entry describes one fireball. } missiles: array[1..maxms] of missile; { Each entry describes one missile. } nMissiles: INTEGER; { Number of currently active missiles. } lastTick: LONGINT; { System clock at time of previous update. } gameOver: BOOLEAN; { Set true when all cities are destroyed. } PauseFlag: BOOLEAN; { True when game is being paused. } Playing: BOOLEAN; { True except buring bonus points and GAME OVER screens. } esFlag: BOOLEAN; { True if "GAME OVER" is to be drawn after exiting main loop. } enddelay: INTEGER; { Used to wait a while after end of round. } citiesLeft: INTEGER; { Number of cities still alive. } bcities: INTEGER; { Number of bonus cities they have left. } cities: array[1..nCities] of BOOLEAN; { State of each city : false = destroyed. } cities2: array[1..nCities] of BOOLEAN; { State of the city before the round began. } nukeHeight: INTEGER; { Vertical position of fireball required to destroy city. } endv: INTEGER; { Y-coordinate of the end of a missile's path. } MirvRate: INTEGER; mirvHeight: INTEGER; { Height at which Mirvs MIRV. } mirvNasty: INTEGER; { Percent chance of sub-missile on mirv for each city. } msSpeed: INTEGER; { Vertical speed of missiles, in 1/16ths of a pixel.} msRate: INTEGER; { Percent rate of missile production. } RoundNumber: INTEGER; { Number of the current round. } Score: LONGINT; { The player's current score. } DisScore: LONGINT; { Score currently displayed on the screen. } HighScore: LONGINT; { The current highest score. } RoundH: INTEGER; Scoreh: INTEGER; { Horiz. position of text 'Score:' } ScoreNumH: INTEGER; { Horiz. position of score. } statsH: INTEGER; { Horiz. position of text 'Enemy left:' } statsNumH: INTEGER; statsRect: Rect; eLeft: INTEGER; { How many enemy missiles are left this round. } yLeft: INTEGER; { How many missiles you (the player) have left. } eDestroyed: INTEGER; { Number of enemy missiles destroyed. } eDesH: INTEGER; { Horiz. position of text 'Enemy Destroyed:' } eDesNumH: INTEGER; { Horiz. position of # of enemy missiles destroyed. } gameSpeed: INTEGER; { Number of ticks between each AdvanFb call. } StartRound: INTEGER; { Round to start off with. } mFlag1: BOOLEAN; { Do missiles aim for dead cities? } MFlag2: INTEGER; { Extent to which enemy missiles blow up. } MExists: array[1..3] of BOOLEAN; { Does this type of missile occur? } MPBase: array[1..3] of INTEGER; { Value of missile at first round... } MPExtra: array[1..3] of INTEGER; { ...plus this much each additional round... } MPoints: array[1..3] of INTEGER; { ...is this much in all. } { %subtitle 'Init routine for menus.' } procedure SetUpMenus; var i: INTEGER; begin myMenus[1] := GetMenu(appleMenu); { Load menu from disk. } {MyMenus[1]^^.MenuData[1] := Chr(AppleSymbol); } { Change title to the Apple symbol. } AddResMenu(myMenus[1], 'DRVR'); { Add items for desk accessories. } myMenus[2] := GetMenu(fileMenu); myMenus[3] := GetMenu(editMenu); myMenus[4] := GetMenu(GameMenu); for i := 1 to lastMenu do InsertMenu(myMenus[i], 0); DrawMenuBar; end; { of SetUpMenus } { %subtitle 'Init routine for fireball BitMaps.' } { This routine is called by the main Init routine, below. It creates a} { bunch of bit images of circles off-screen, which can later be drawn on} { the screen much faster than FrameOval. } procedure InitFbs; var i: FIXED; aRect: Rect; { Rectangle for drawing FrameOvals. } saveBits: BitMap; { To save the current GrafPort while drawing off-screen. } SaveRect: Rect; begin SaveBits := ThePort^.PortBits; SaveRect := ThePort^.PortRect; fbBitMap.rowBytes := 8; SetRect(fbBitMap.bounds, 0, 0, 60, 60); ThePort^.portRect := fbBitMap.Bounds; PenSize(fbRadRate, fbRadRate); PenPat(black); PenMode(PatCopy); SetRect(aRect, 30, 30, 30, 30); { Start with an empty rectangle in the center. } for i := 0 to 14 do begin fbBitMap.baseAddr := @fbBits[240 * i + 1]; { Pick starting address for this picture. } insetRect(aRect, -2, -2); { Make rect a bit larger. } SetPortBits(fbBitMap); EraseRect(thePort^.portBits.bounds); { Erase to all white. } FrameOval(aRect); { Draw the circle. } end; SetPortBits(SaveBits); { Return to normal screen drawing. } ThePort^.PortRect := SaveRect; end; { of InitFbs } { %subtitle 'Init routine' } procedure Setup; var wRect: Rect; { Window Rectangle. } i: INTEGER; begin { Macintosh System initialization. } InitGraf(@thePort); { Quickdraw. } InitFonts; { Font Manager. } InitWindows; { Window Manager. } InitMenus; { Menu Manager. } TEInit; { TextEdit. } InitDialogs(nil); { Dialog manager. } InitCursor; { Cursor handler. } InitAllPacks; { Package Manager. } SetUpMenus; { My routine to insert the menus. } hCurs := POINTER(GetCursor(256)); { Load the Crosshairs cursor that I use in the game. } hCurs2 := POINTER(GetCursor(257)); { Load the Crosshairs cursor that I use in the game. } crosshairs := hCurs^^; crosshairs2 := hCurs2^^; SetCursor(crosshairs); CityPattern := GetPattern(256); screenRect := screenBits.bounds; { Don't actually use this, but might later. } doneFlag := FALSE; { This flag is set to False when user selects 'Quit'. } HighScore := 0; GameSpeed := 8; { Set up all the user-settable options. } StartRound := 0; MFlag1 := True; MFlag2 := 1; for i := 1 to 3 do begin MExists[i] := True; MPBase[i] := 10 * i; MPExtra[i] := 10 * i; end; { myWindow := GetNewWindow(256, @wRecord, POINTER(-1)); } wRect := ScreenRect; wRect.top := 20; myWindow := NewWindow(@wRecord, wRect, 'Missile Command by Robert Munafo', True, 0, POINTER(-1), False, 0); { BoundsRect } { Visible } { ProcID } { behind } { GoAwayFlag } { RefCon } SetPort(myWindow); { Might as well play the game in a window... } FieldWidth := MyWindow^.portRect.right; { These variables are used by the game. } FieldHeight := MyWindow^.portRect.bottom; TextFont(0); TextFace([]); InitFbs; { Set up the fireball BitMaps. } second_string := GetString(str_2_ID); { Get strings from resource. } first_string := GetString(str_1_ID); FlushEvents(everyEvent, 0); end; { %Subtitle 'Gets a text string' } { This routine conducts a dialog to get a text string from the} { user. The function value returned indicates whether the user stopped by entering} { OK or cancel. The ID is the dialog ID number to use and Text is the} { text they typed in. To get the text, we display the dialog window,} { get the handle of the edit text and execute a small event loop wich waits} { until one of the two buttons is pressed. When done, we dispose of the dialogue} { window, set the flag depending on the means of exiting and return the text.} { The predefined dialogue window has the following items in it:} {} {Item Number Item} { 1 OK button, Enabled} { 2 Cancel button, Enabled} { 3 EditText area, Enabled} { 4 StatText area, Disabled (Prompt)} { 5... Anything else, Disabled} {*) { the actual function has been deleted. } { %subtitle '"About Missile" routine.' } { This routine is nearly identical to Jason Ansley's About_Windows routine. } procedure AboutMissile; var item_chosen: integer; {which botton was hit (I only have one so it doesn't really matter)} item_type: integer; {type of item in res. def. file (needed to pass as parameter)} first_handle, second_handle: Handle; {handles to my items} the_dialog: DialogPtr; {a pointer to my dialog box} box: rect; {the display rectangle of the item} begin the_dialog := GetNewDialog(box_ID, nil, pointer(-1)); {get dialog box from resource file} GetDItem(the_dialog, 2, item_type, first_handle, box); {get text format info from file} GetDItem(the_dialog, 3, item_type, second_handle, box); SetIText(first_handle, first_string^^); {set the lines of text into the box} SetIText(second_handle, second_string^^); ModalDialog(nil, item_chosen); {operate the box} DisposDialog(the_dialog); {get rid of the box now that I'm done with it} end; { %subtitle 'My own random number routine.' } function Rnd (n: FIXED): FIXED; { Function to generate a random integer from} { 1 to N. } begin Rnd := (ABS(Random) mod n) + 1; end; { %subtitle 'DrawNumber - converts number to string and draws it on the screen.' } procedure DrawNumber (n: LONGINT); var s: Str255; begin NumToString(n, s); DrawString(s); end; { %subtitle 'Update the score at the bottom of the screen.' } procedure UpdScore; begin TextMode(SrcBic); MoveTo(ScoreNumH, FieldHeight - 5); TextSize(24); DrawNumber(disScore); TextMode(SrcOr); MoveTo(ScoreNumH, FieldHeight - 5); DrawNumber(Score); disScore := Score; EraseRect(statsRect); TextMode(SrcOr); MoveTo(statsNumH, FieldHeight - 20); TextSize(12); DrawNumber(eLeft); MoveTo(statsNumH, FieldHeight - 5); DrawNumber(yLeft); TextMode(SrcCopy); MoveTo(eDesNumH, FieldHeight - 20); DrawNumber(eDestroyed); end; { %subtitle 'Print the score.' } procedure DrawScore; begin TextMode(srcCopy); MoveTo(ScoreH, FieldHeight - 14); TextSize(12); DrawString('Score: '); MoveTo(ScoreNumH, FieldHeight - 5); TextSize(24); DrawNumber(Score); disScore := Score; TextSize(12); MoveTo(statsH, FIeldHeight - 20); DrawString('Enemy left: '); DrawNumber(eLeft); MoveTo(statsH, FieldHeight - 5); DrawString('Yours left: '); MoveTo(statsNumH, FieldHeight - 5); DrawNumber(yLeft); MoveTo(eDesH, FieldHeight - 20); DrawString('Destroyed: '); MoveTo(eDesNumH, FieldHeight - 20); DrawNumber(eDestroyed); end; { %subtitle 'Print round #, score, and # killed.' } procedure BottomLine; begin ; TextSize(12); RoundH := 10 + StringWidth('Round: '); ScoreH := RoundH + 2 * StringWidth('00 '); ScoreNumH := ScoreH + StringWidth('Score: '); eDesH := FieldWidth - StringWidth('Destroyed: 0000 '); eDesNumH := FieldWidth - StringWidth('0000 '); statsH := eDesH - StringWidth('Enemy left: 000 '); statsNumH := eDesH - StringWidth('000 '); SetRect(statsRect, statsNumH, FieldHeight - 30, eDesH - 2, FieldHeight); TextMode(srcCopy); MoveTo(10, FieldHeight - 14); DrawString('Round: '); MoveTo(RoundH, FieldHeight - 5); TextSize(24); DrawNumber(RoundNumber); DrawScore; MoveTo(eDesH, FieldHeight - 5); DrawString('High Score: '); DrawNumber(HighScore); end; { %subtitle 'Create a new fireball.' } procedure AllocFb (location: Point); var i: INTEGER; idlefb: INTEGER; begin idlefb := 0; for i := 1 to maxfb do if (fbs[i].mode = fbIdle) and (idlefb = 0) then idlefb := i; if idlefb <> 0 then begin SetRect(fbs[idlefb].bounds, location.h - 30, location.v - 30, location.h + 30, location.v + 30); fbs[idlefb].size := 0; fbs[idlefb].mode := fbGrow; end; end; { of AllocFb } { %subtitle 'Advance (animate) the fireballs.' } procedure AdvanFb; var i: INTEGER; aRect: Rect; fbrect: Rect; { Rectangle for drawing fireballs. } begin PenSize(fbRadRate, fbRadRate); PenPat(black); SetRect(aRect, 0, 0, 60, 60); for i := 1 to maxfb do begin if fbs[i].mode <> fbidle then case fbs[i].mode of fbGrow: begin fbs[i].size := fbs[i].size + 1; fbBitMap.baseAddr := @fbBits[240 * fbs[i].size - 239]; { Select a fireball picture. } CopyBits(fbBitMap, thePort^.PortBits, aRect, fbs[i].bounds, SrcOr, nil); if fbs[i].size >= fbRad then fbs[i].mode := fbShrink; end; fbShrink: begin fbBitMap.baseAddr := @fbBits[240 * fbs[i].size - 239]; { Select the correct fireball picture. } CopyBits(fbBitMap, thePort^.PortBits, aRect, fbs[i].bounds, SrcBic, nil); fbs[i].size := fbs[i].size - 1; if fbs[i].size = 0 then fbs[i].mode := fbIdle; end; end; { of mode case } end; { of loop } end; { of AdvanFb } { %subtitle 'Create a new enemy missile.' } procedure AllocMs (position: Point; city: INTEGER; kind: INTEGER); var i: INTEGER; idleMs: INTEGER; targetcity: integer; targeth: integer; begin idlems := 0; for i := 1 to maxMs do if (missiles[i].mode = msIdle) and (idlems = 0) then idlems := i; if idlems <> 0 then begin i := idlems; missiles[i].start.v := position.v; { Set coordinates for beginning of path. } missiles[i].start.h := position.h; missiles[i].pos.v := missiles[i].start.v * 16; { Initial current position is } missiles[i].pos.h := missiles[i].start.h * 16; { at start of path.o} missiles[i].dv := msSpeed; { Speed of missile. } missiles[i].city := city; targeth := (((missiles[i].city * 2 - 1) * FieldWidth) div (2 * nCities)) * 16; missiles[i].dh := (targeth - (missiles[i].start.h * 16)) div (((endv - missiles[i].start.v) * 16) div missiles[i].dv); missiles[i].kind := kind; missiles[i].mode := msActive; nMissiles := nMissiles + 1; end; end; function BlackPixel (aPoint: Point): BOOLEAN; begin aPoint.h := aPoint.h div 16; aPoint.v := aPoint.v div 16; BlackPixel := (GetPixel(aPoint.h, aPoint.v) and GetPixel(aPoint.h + 1, aPoint.v)); end; { %subtitle 'Advance (animate) the enemy missiles.' } procedure AdvanMs; var i, j: INTEGER; newpos, newpixel: Point; detonate: BOOLEAN; Points: INTEGER; begin penMode(PatCopy); for i := 1 to maxms do begin if missiles[i].mode <> msIdle then begin newpos.v := missiles[i].pos.v + missiles[i].dv; newpos.h := missiles[i].pos.h + missiles[i].dh; newpixel.v := newpos.v div 16; newpixel.h := newpos.h div 16; detonate := FALSE; if BlackPixel(newpos) or BlackPixel(missiles[i].pos) then detonate := TRUE; PenSize(mWidth, mWidth); PenPat(gray); MoveTo(missiles[i].pos.h div 16, missiles[i].pos.v div 16); LineTo(newpixel.h, newpixel.v); missiles[i].pos := newpos; if (missiles[i].kind = msMirv) and (newpixel.v > mirvHeight) then { If it's a Mirv, } { and it's reached the right altitude, } begin { then make lots of little missiles... } for j := 1 to nCities do if (j <> missiles[i].city) and (Rnd(100) < mirvNasty) then allocMs(newpixel, j, msNormal); missiles[i].kind := msNormal; end; if (newpos.v div 16 >= endv) or detonate then { If the missile has reached its target } { or hit a fireball, } begin PenSize(mWidth + 2, mWidth + 2); PenPat(white); MoveTo(missiles[i].start.h - 1, missiles[i].start.v - 1); { Erase the line. } LineTo(newpixel.h - 1, newpixel.v - 1); missiles[i].mode := msIdle; { turn off the missile, } nMissiles := nMissiles - 1; if detonate then begin if (MFlag2 = 2) or ((MFlag2 = 1) and (Rnd(25) > RoundNumber)) then allocFb(newpixel); { Make a new fireball. } eDestroyed := eDestroyed + 1; Score := Score + MPoints[missiles[i].kind]; UpdScore; end else if cities[missiles[i].city] then begin allocfb(newpixel); cities[missiles[i].city] := false; citiesLeft := citiesLeft - 1; if citiesLeft + bCities = 0 then gameOver := true; end; end; { Of detonate routine. } end; { Of THEN clause for this active missile. } end; { of missile loop } end; { of AdvanMs } { %subtitle 'Print a string in the center of the window.' } procedure Centre (TheText: Str255; posV: INTEGER); begin MoveTo((FieldWidth - StringWidth(TheText)) div 2, posV); DrawString(TheText); end; { %subtitle 'Clear the screen.' } procedure ClearScreen; var aRect: Rect; begin aRect.top := 0; aRect.left := 0; aRect.bottom := FieldHeight; aRect.right := FieldWidth; FillRect(aRect, white); end; { %subtitle 'Draw a city.' } procedure DrawCity (city: INTEGER); var CityLoc: Point; bOffset: INTEGER; aRect: Rect; begin ; RandSeed := 2; { Make each city look the same. } cityloc.v := FieldHeight - CityHeight; cityloc.h := ((city * 2 - 1) * FieldWidth) div (2 * nCities); bOffset := -(CityWidth div 2); while bOffset < (CityWidth div 2) do begin aRect.left := cityloc.h + bOffset; aRect.top := cityloc.v - Rnd(BuildHeight); aRect.bottom := cityloc.v + 2; aRect.right := aRect.left + BuildWidth; FillRect(aRect, CityPattern^^); bOffset := bOffset + BuildWidth; end; end; { %subtitle 'Draw cities and bottom line.' } procedure DrawStuff; var i: INTEGER; aRect: Rect; begin ClearScreen; for i := 1 to nCities do if cities[i] then DrawCity(i); PenPat(black); RandSeed := TickCount; { Randomize } BottomLine; end; { of DrawStuff } { %subtitle 'MinMax - Convert Num to String with range checking. } function MinMax (TheString: Str255; minimum: INTEGER; Maximum: INTEGER): INTEGER; var TheNumber: LONGINT; begin StringToNum(TheString, TheNumber); if TheNumber < Minimum then TheNumber := Minimum; if TheNumber > Maximum then TheNumber := Maximum; MinMax := TheNumber; end; { %subtitle 'DoGameOptions - Does the big dialog box.' } procedure DoGameOptions; const { Here are all the magic constants that go with that monster dialog box: } OK = 1; { OK button } GSpeed = 5; { Game Speed text box } SRound = 7; { Start Round text box } MPB = 18; { MPBase text boxes } MPE = 24; { MPExtra text boxes } MF1 = 8; { MFlag1 check box } MF2 = 10; { MFlag2 radio buttons } MEX = 15; { MExists check boxes } var savePort: grafptr; { For saving the GrafPort and restoring later. } DStorage: DialogRecord; { Storage for my dialog box. } DPtr: DialogPtr; { Pointer to my dialog box. } ItemHit: integer; { Item that was just hit by the user. } TheType: integer; { not used } TheHandle: Handle; { Temp. handle } TheRect: Rect; { not used. } TheString: str255; { Temp. string } TheValue: INTEGER; { Temp. integer value. } i: INTEGER; { Loop Variable. } Station: INTEGER; { Current setting of radio buttons. } begin DPtr := getNewDialog(box2_id, @DStorage, pointer(-1)); { Load the data from disk . . . } GetDItem(Dptr, GSpeed, theType, theHandle, theRect); { Set up all the EditText boxes } NumToString(GameSpeed, TheString); SetIText(theHandle, TheString); GetDItem(Dptr, SRound, theType, theHandle, theRect); NumToString(StartRound, TheString); SetIText(theHandle, TheString); for i := 0 to 2 do begin GetDItem(Dptr, MPB + i, theType, theHandle, theRect); { Text for points each missile is worth. } NumToString(MPBase[i + 1], TheString); SetIText(theHandle, TheString); GetDItem(Dptr, MPB + 3 + i, theType, theHandle, theRect); { Text for plus sign. } TheString := '+'; SetIText(theHandle, TheString); GetDItem(Dptr, MPE + i, theType, theHandle, theRect); { Text for additional points each round } NumToString(MPExtra[i + 1], TheString); SetIText(theHandle, TheString); GetDItem(Dptr, MPE + 3 + i, theType, theHandle, theRect); { text : '* round' } TheString := '* round'; SetIText(theHandle, TheString); GetDItem(Dptr, MEX + i, theType, theHandle, theRect); { Check boxes for types of missiles } TheValue := 0; if MExists[i + 1] then TheValue := 1; SetCtlValue(Pointer(TheHandle), TheValue); end; for i := 0 to 2 do begin { Program the radio buttons. } GetDItem(Dptr, MF2 + i, theType, theHandle, theRect); TheValue := 0; if MFlag2 = i then TheValue := 1; SetCtlValue(Pointer(TheHandle), TheValue); end; Station := MF2 + MFlag2; GetDItem(Dptr, MF1, theType, theHandle, theRect); { Set check box for MFlag1. } TheValue := 0; if MFlag1 then TheValue := 1; SetCtlValue(Pointer(TheHandle), TheValue); SelIText(DPtr, sRound, 0, 1000); { Initial selection is StartRound. } repeat ModalDialog(nil, ItemHit); { Let them hit an item... } if ItemHit in [10, 11, 12] then { Was it one of the radio buttons? } begin GetDItem(Dptr, Station, theType, theHandle, theRect); { Get the old one, and turn it off. } SetCtlValue(Pointer(TheHandle), 0); Station := ItemHit; GetDItem(Dptr, Station, theType, theHandle, theRect); { Turn this one on. } SetCtlValue(Pointer(TheHandle), 1); end; if ItemHit in [8, 15, 16, 17] then { Was it a check box? } begin GetDItem(Dptr, ItemHit, TheType, TheHandle, TheRect); TheValue := 1 - GetCtlValue(Pointer(TheHandle)); { Find the value and invert it. } SetCtlValue(Pointer(TheHandle), TheValue); end; until ItemHit in [1, 2]; if itemHit = OK then begin { Have to get all the new values now! } GetDItem(Dptr, GSpeed, theType, theHandle, theRect); { Set up all the EditText boxes } GetIText(theHandle, TheString); GameSpeed := MinMax(TheString, 1, 99); GetDItem(Dptr, SRound, theType, theHandle, theRect); GetIText(theHandle, TheString); StartRound := MinMax(TheString, 1, 99); for i := 0 to 2 do begin GetDItem(Dptr, MPB + i, theType, theHandle, theRect); { Text for points each missile is worth. } GetIText(theHandle, TheString); MPBase[i + 1] := MinMax(TheString, -1000, 1000); GetDItem(Dptr, MPE + i, theType, theHandle, theRect); { Text for additional points each round } GetIText(theHandle, TheString); MPExtra[i + 1] := MinMax(TheString, -1000, 1000); GetDItem(Dptr, MEX + i, theType, theHandle, theRect); { Check boxes for types of missiles } if GetCtlValue(Pointer(TheHandle)) = 0 then MExists[i + 1] := False else MExists[i + 1] := True; end; for i := 0 to 2 do begin { radio buttons. } GetDItem(Dptr, MF2 + i, theType, theHandle, theRect); if GetCtlValue(Pointer(TheHandle)) = 1 then MFlag2 := i; end; GetDItem(Dptr, MF1, theType, theHandle, theRect); { MFlag1. } if GetCtlValue(Pointer(TheHandle)) = 0 then MFlag1 := False else MFlag1 := True; end; {if itemHit = OK} DisposDialog(DPtr); GetPort(savePort); {save whatever port was current} SetPort(MyWindow); InvalRect(ScreenRect); {force the entire screen, including frame, to be redrawn} SetPort(savePort); end; { of DoGameOptions } { %subtitle 'DoCommand - Does all the menu commands.' } procedure DoCommand (mResult: LongInt); var name: STR255; theMenu, theItem: INTEGER; { Menu and item numbers. } dummy: BOOLEAN; begin theMenu := HiWord(mResult); theItem := LoWord(mResult); case theMenu of appleMenu: { About Missile and Desk Accessories. } if theItem = 1 then AboutMissile else begin GetItem(myMenus[1], theItem, name); refNum := OpenDeskAcc(name); end; fileMenu: { There is currently only one option in this menu. } begin doneFlag := TRUE; PauseFlag := False; esFlag := False; end; EditMenu: { Cut, copy, paste, and undo. } begin dummy := SystemEdit(theItem - 1); { Can't cut and paste in game window. } end; { of editMenu } GameMenu: begin { SetPort(myWindow); } case theItem of 1: PauseFlag := True; { pause game } 2: PauseFlag := False; { resume game } 3: ; { Empty line in menu. } 4: DoGameOptions; 5: begin { New Game. } GameOver := True; EndDelay := 100; esFlag := False; PauseFlag := False; end; end; { of item case } end; { of editMenu } end; { of menu case } HiliteMenu(0); end; { of DoCommand } { %subtitle 'Routine to check for events.' } procedure CheckEvents; var MousePoint: Point; MouseCode: INTEGER; TheChar: CHAR; i: FIXED; fbrect: Rect; { Rectangle for drawing fireballs. } begin repeat SystemTask; GetMouse(MousePoint); LocalToGlobal(MousePoint); MouseCode := FindWindow(MousePoint, whichWindow); if FrontWindow = MyWindow then { If my window is activated, set cursor: } if (WhichWindow = MyWindow) and (Mousecode <> inMenuBar) and (not PauseFlag) and (FrontWindow = MyWindow) and (yLeft > 0) then { If it's above my window, } { but not the scroll bar, } { the game is 'active', } { my window is in front, } { and the user has missiles left, then: } SetCursor(crosshairs) { Set the Missile Command cursor. } else SetCursor(crosshairs2); { Otherwise, use an arrow. } while GetNextEvent(everyEvent, myEvent) do case myEvent.what of mouseDown: begin code := FindWindow(myEvent.where, whichWindow); case code of inMenuBar: DoCommand(MenuSelect(myEvent.where)); inSysWindow: SystemClick(myEvent, whichWindow); inDrag: ; { Can't drag game window - it's too hard to} { figure out collisions off-screen. } inGrow, inContent: begin if (whichWindow <> FrontWindow) and (WhichWindow <> MyWindow) then { Can't select MyWindow - must close other windows. } SelectWindow(whichWindow) else if (not PauseFlag) and (FrontWindow = MyWindow) and (yLeft > 0) then { Can't make fireballs while paused. } { Game must be running } { Make sure they have some missiles left. } begin GlobalToLocal(myEvent.where); if myEvent.where.v > NukeHeight then { If it's too low, } myEvent.where.v := NukeHeight; { make it legal. } AllocFb(myEvent.where); { Put a fireball in the list. } yLeft := yLeft - 1; { They have less missiles now... } updScore; { let them know. } end; end; end; { of code case } end; { of mouseDown } keyDown: { No Autokey - don't want to repeat Option-N. } if ((MyEvent.modifiers div 256) mod 2 <> 0) then { If command key was held down } begin TheChar := CHR(myEvent.message mod 256); DoCommand(MenuKey(TheChar)); end; updateEvt: begin SetPort(myWindow); BeginUpdate(myWindow); if Playing then begin for i := 1 to nCities do if cities[i] then begin DrawCity(i); end; RandSeed := TickCount; { Randomize after drawing cities. } for i := 1 to maxMs do if missiles[i].mode = msActive then begin PenSize(mWidth, mWidth); PenPat(Gray); MoveTo(missiles[i].start.h, missiles[i].start.v); LineTo(missiles[i].pos.h div 16, missiles[i].pos.v div 16); end; for i := 1 to maxFb do if fbs[i].mode <> fbIdle then begin fbRect := fbs[i].bounds; insetRect(fbRect, 30 - 2 * fbs[i].size, 30 - 2 * fbs[i].size); PenMode(PatCopy); FillOval(fbRect, Black); end; end; BottomLine; EndUpdate(myWindow); end; { of updateEvt } end; { of event case } until (FrontWindow = MyWindow) and not PauseFlag; { If another window has been selected, } { don't do anything except process events } { until game window is re-selected. } end; { of CheckEvents } { %subtitle 'Print the GAME OVER message.' } procedure EndScreen; var aRect: Rect; i: INTEGER; GameOffset, OverOffset: INTEGER; Center: Point; begin TextSize(72); TextMode(srcBic); PenMode(patOr); PenPat(Black); PenSize(5, 5); GameOffset := StringWidth('GAME') div 2; OverOffset := StringWidth('OVER') div 2; Center.v := FieldHeight div 2; Center.h := FieldWidth div 2; SetRect(aRect, Center.h, Center.v, Center.h, Center.v); for i := 1 to 30 do begin CheckEvents; InsetRect(aRect, -5, -5); FrameOval(aRect); TextSize(72); TextMode(srcBic); Centre('GAME', Center.v - 12); Centre('OVER', Center.v + 60); end; PenMode(patBic); for i := 1 to 30 do begin CheckEvents; FrameOval(aRect); InsetRect(aRect, 5, 5); end; end; { of EndScreen } { %subtitle 'Play a single game.' } procedure PlayGame; var i: INTEGER; s: Str255; msPoint: Point; msCity, msKind: INTEGER; rMissiles: INTEGER; { "round missiles" - number to set eLeft to each round. } yMissiles: INTEGER; { "your Missiles" - similar to rMissiles. } RoundOver: BOOLEAN; aString: Str255; Points: INTEGER; procedure InitRound; var i: INTEGER; begin RoundOver := False; endDelay := 0; Playing := True; DrawStuff; { Redraw screen. } for i := 1 to maxfb do fbs[i].mode := fbIdle; for i := 1 to maxms do missiles[i].mode := msIdle; for i := 1 to 3 do MPoints[i] := MPBase[i] + MPExtra[i] * RoundNumber; for i := 1 to nCities do cities2[i] := cities[i]; nMissiles := 0; MirvRate := 0; mirvHeight := FieldHeight div 4; mirvNasty := 30; msSpeed := 60; if RoundNumber > 2 then MirvRate := 10; if RoundNumber > 4 then msSpeed := 80; if RoundNumber > 6 then MirvRate := 20; if RoundNumber > 8 then MirvNasty := 50; if RoundNumber > 10 then MirvHeight := FieldHeight div 3; if RoundNumber > 12 then msSpeed := 90; if RoundNumber > 14 then MirvRate := 30; if RoundNumber > 19 then msSpeed := 100; msRate := 5 + RoundNumber; rMissiles := (RoundNumber * RoundNumber) div 6 + RoundNumber + 6; { Compute # of enemy missiles } yMissiles := rMissiles + RoundNumber; { Adjust yMissiles accordingly. } eLeft := rMissiles; yLeft := yMissiles; RoundNumber := RoundNumber + 1; if (RoundNumber mod 5) = 0 then bCities := bCities + 1; BottomLine; FlushEvents(everyEvent, 0); { Ignore unprocessed events from previous round. } end; procedure EndBonus; var i: INTEGER; begin ; ClearScreen; BottomLine; TextSize(24); TextMode(srcCopy); aString := 'End of round '; aString[15] := Chr(48 + (RoundNumber mod 10)); if RoundNumber > 9 then aString[14] := Chr(48 + (RoundNumber div 10)); if CitiesLeft > 0 then begin { give bonus points for cities } Centre(aString, 100); Centre('Bonus:', 130); Points := 0; for i := 1 to nCities do if (cities[i] and not DoneFlag) then begin LastTick := TickCount; DrawCity(i); Points := Points + RoundNumber * 20; Score := Score + RoundNumber * 20; TextSize(24); NumtoString(Points, S); Centre(S, 160); updScore; repeat CheckEvents; until (TickCount >= LastTick + 30) or DoneFlag; end end; { of bonus for cities. } if (bCities > 0) and (CitiesLeft < 6) then begin { Give a bonus city. } LastTick := TickCount; TextSize(24); Centre('* Bonus City *', 190); CitiesLeft := CitiesLeft + 1; bCities := bCities - 1; repeat i := Rnd(6) until (not Cities[i]); Cities[i] := true; repeat CheckEvents; until (TickCount >= LastTick + 60) or DoneFlag; end { of bonus city routine. } end; { of EndBonus } begin RoundNumber := StartRound; { Start out at this round. } Score := 0; eDestroyed := 0; for i := 1 to nCities do cities[i] := true; citiesLeft := nCities; bCities := 0; NukeHeight := FieldHeight - CityHeight - (BuildHeight div 2) - fbRad * fbRadRate; endv := FieldHeight - CityHeight - (BuildHeight div 2); GameOver := False; esFlag := True; lastTick := TickCount; repeat { Repeat loop for playing rounds. } InitRound; repeat { Repeat loop for animating objects in game. } CheckEvents; if TickCount >= lastTick + gameSpeed then { If enough time has elapsed since the} { last update, update objects on screen. } begin lastTick := TickCount; if (eLeft <= 0) and (nMissiles = 0) then RoundOver := True; if RoundOver or GameOver then endDelay := endDelay + 1 else if (rnd(100) < msRate) and (eLeft > 0) then begin { Launch an enemy missile every now and then. } eLeft := eLeft - 1; msPoint.v := 0; msPoint.h := Rnd(FieldWidth - 4); repeat msCity := Rnd(nCities); { Pick a city. } until ((Rnd(3) = 1) and mFlag1) or cities2[msCity]; { Try again for most missiles if city was destroyed. } msKind := 0; if MExists[msNormal] then msKind := msNormal; if (rnd(100) <= MirvRate) and MExists[msMirv] then msKind := msMirv; if msKind = 0 then msKind := msNormal; AllocMs(msPoint, msCity, msKind); end; AdvanFb; { Advance state of fireballs. } AdvanMs; { Advance position of missiles. } end; until ((GameOver or RoundOver) and (endDelay > 30)) or doneFlag; { End of loop to move objects on screen. } Playing := False; if esFlag and (CitiesLeft + bCities > 0) and not DoneFlag then EndBonus; until GameOver or DoneFlag; { End of loop to play rounds. } if Score > HighScore then HighScore := Score; if esFlag then EndScreen; end; { of PlayGame } { %subtitle 'Main program.' } begin { main program } SetUp; repeat PlayGame; until doneFlag; end.